package com.moyq5.mvc.framework.dao;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import javax.persistence.Column;
import javax.persistence.Embeddable;
import javax.persistence.Embedded;
import javax.persistence.EmbeddedId;
import javax.persistence.Entity;
import javax.persistence.EntityManager;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.MappedSuperclass;
import javax.persistence.PersistenceContext;
import javax.persistence.criteria.AbstractQuery;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Path;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import javax.persistence.criteria.Subquery;

/**
 * @author Moyq5
 * @date 2018年8月27日
 * @param <T>
 */
public abstract class ExampleDao<T> {
	
	@PersistenceContext	
	private EntityManager em;
	private Class<T> clazz;
	public ExampleDao(Class<T> clazz) {
		this.clazz = clazz;
	}
	
	protected <E> CriteriaQuery<E> setExample(CriteriaQuery<E> cq, Root<E> root, E entity) {
		return cq.where(examplePredicates(cq, root, entity));
	}

	protected <E> Subquery<E> setExample(Subquery<E> sq, Root<E> root, E entity) {
		return sq.where(examplePredicates(sq, root, entity));
	}
	
	protected ExampleQuery<T> createExampleQuery(String alias, T entity, String searchWord) {
		return createExampleQuery(alias, entity);
	}
	
	private ExampleQuery<T> createExampleQuery(String alias, T entity) {
		CriteriaBuilder cb = em().getCriteriaBuilder();
		CriteriaQuery<T> cq = (CriteriaQuery<T>)cb.createQuery(clazz);
		Root<T> root = (Root<T>)cq.from(clazz);
		if (null == alias || alias.isEmpty()) {
			root.alias(clazz.getSimpleName());
		} else {
			root.alias(alias);
		}
		cq = setExample(cq, root, entity);
		cq.select(root);
		return new ExampleQuery<T>(cq);
	}
	
	private <E> Predicate[] examplePredicates(AbstractQuery<?> cq, Root<?> root,E entity) {
		CriteriaBuilder cb = em().getCriteriaBuilder();
		List<Predicate> predicates = examplePredicates(new ArrayList<Predicate>(), cq, root, entity);
		if (predicates.size() == 0) {
			predicates.add(cb.equal(cb.literal(1), 1));
		}
		return predicates.toArray(new Predicate[predicates.size()]);
	}
	
	private <E> List<Predicate> examplePredicates(List<Predicate> predicates, AbstractQuery<?> cq, Path<?> path, E entity) {
		CriteriaBuilder cb = em().getCriteriaBuilder();
		Class<?> clazz = entity.getClass();
		List<Field> fields = getFeilds(clazz);
		for (Field field: fields) {
			if (field.isAnnotationPresent(Column.class)
					|| field.isAnnotationPresent(JoinColumn.class)
					|| field.isAnnotationPresent(Embedded.class)
					|| field.isAnnotationPresent(EmbeddedId.class)) {
				field.setAccessible(true);
				Object value;
				try {
					// 用get方法是为了触发懒加载
					Method method = clazz.getMethod("get" + field.getName().substring(0, 1).toUpperCase() + field.getName().substring(1));
					value = method.invoke(entity);
				} catch (Exception e) {
					throw new RuntimeException(e);
				}
				if (null != value && !"".equals(value)) {
					if (!value.getClass().isAnnotationPresent(Entity.class)
							&& !value.getClass().getSuperclass().isAnnotationPresent(Entity.class)
							&& !value.getClass().isAnnotationPresent(Embeddable.class)) {
						predicates.add(cb.equal(path.get(field.getName()), value));
					} else {
						predicates = examplePredicates(predicates, cq, path.get(field.getName()), value);
					}
					if (null != field.getAnnotation(Id.class)) {
						break;
					}
				}
				
			}
		}
		return predicates;
	}
	
	private <E> List<Field> getFeilds(Class<?> clazz) {
		List<Field> list = new ArrayList<Field>();
		if (null != clazz.getAnnotation(Entity.class) 
				|| null != clazz.getAnnotation(MappedSuperclass.class)
				|| null != clazz.getAnnotation(Embeddable.class)) {
			Field[] fields = clazz.getDeclaredFields();
			if (fields.length > 0) {
				Collections.addAll(list, fields);
			}
		}
		
		Class<?> superClazz;
		if ((superClazz = clazz.getSuperclass()) != Object.class) {
			List<Field> superFields = getFeilds(superClazz);
			if (superFields.size() > 0) {
				list.addAll(superFields);
			}
		}
		return list;
	}
	
	protected EntityManager em() {
		return em;
	}
	

}
